package com.hang.jvm.risc.v1_adder;

import cn.hutool.core.io.FileUtil;
import com.hang.jvm.risc.RegisterFile2;
import lombok.extern.slf4j.Slf4j;

import java.math.BigInteger;

@Slf4j
public class Cpu {

    private RegisterFile2 registerFile;

    public long pc = 0;

    public byte[] dram;

    private static final long DRAM_SIZE = 1024 * 1024 * 128L;

    private static String[] RVABI = {"zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4",
            "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4",
            "t5", "t6"};

    public Cpu(byte[] code) {
        this.registerFile = new RegisterFile2();
        this.registerFile.write(0, BigInteger.ZERO);
        this.registerFile.write(2, BigInteger.valueOf(DRAM_SIZE - 1));
        this.dram = code;
    }

    public int fetch() {
        int byte0 = dram[(int) pc] & 0xff;
        int byte1 = (dram[(int) pc + 1] << 8) & 0xff00;
        int byte2 = (dram[(int) pc + 2] << 16) & 0xff0000;
        int byte3 = (dram[(int) pc + 3] << 24) & 0xff000000;
        int inst = byte3 | byte2 | byte1 | byte0;
        return inst;
    }

    public void execute(int inst) {
        int opcode = inst & 0x7f;
        int rd = (inst >> 7) & 0x1f;
        int rs1 = (inst >> 15) & 0x1f;
        int rs2 = (inst >> 20) & 0x1f;
        BigInteger imm = BigInteger.valueOf((inst & 0xfff00000) >> 20);

        switch (opcode) {
            case 0x13: // addi
                BigInteger result = registerFile.read(rs1).add(imm);
                registerFile.write(rd, result);
                break;
            case 0x33: // add
                BigInteger val1 = registerFile.read(rs1);
                BigInteger val2 = registerFile.read(rs2);
                registerFile.write(rd, val1.add(val2));
                break;
            default:
                throw new UnsupportedOperationException("Unknown opcode: " + opcode);
        }
    }

    public void dumpRegisters() {
        System.out.printf("%s%n", "-----------------------------------registers------------------------------------");
        for (int i = 0; i < 32; i += 4) {
            System.out.printf("x%2s(%4s) = 0x%016X    x%2s(%4s) = 0x%016X    x%2s(%4s) = 0x%016X    x%2s(%4s) = 0x%016X%n", i, RVABI[i], registerFile.read(i), i + 1, RVABI[i + 1], registerFile.read(i + 1), i + 2, RVABI[i + 2], registerFile.read(i + 2), i + 3, RVABI[i + 3], registerFile.read(i + 3));
        }
        System.out.printf("pc = 0x%016X", pc);
    }

    public static void main(String[] args) {
        if (args.length == 0) {
            log.error("请传入bin文件");
            return;
        }
        byte[] code = FileUtil.readBytes(args[0]);
        log.info("code length:{}", code.length);
        Cpu cpu = new Cpu(code);
        while (cpu.pc < cpu.dram.length) {
            int inst = cpu.fetch();
            cpu.execute(inst);
            cpu.pc += 4;
        }
        cpu.dumpRegisters();
    }

}
